﻿#region License and Terms
// MoreLINQ - Extensions to LINQ to Objects
// Copyright (c) 2008 Jonathan Skeet. All rights reserved.
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This class © 2013 Jon Hanna. Contributed to the above project.
// Licenses under the Apache License, Version 2.0 as per above.
#endregion
using System;
using System.Collections;
using System.Collections.Generic;

namespace MoreLinq
{
    /// <summary>
    /// A buffered sequence which lazily iterates through a source enumeration on first
    /// being itself iterated, and afterwards iterates through an in-memory store on
    /// subsequent iterations. It is designed to tolerate partial iterations - using
    /// what it has stored and then continuing to take objects from its source until
    /// exhausted. If the source enumreation implements <see cref="ICollection{T}"/>
    /// it is assumed to already be an in-memory collection, and a second store is not
    /// made.
    /// </summary>
    public class BufferedEnumerable<T> : IEnumerable<T>
    {
        private class Enumerator : IEnumerator<T>
        {
            private readonly BufferedEnumerable<T> _parent;
            private readonly IEnumerator<T> _etor;
            private T _current;
            public Enumerator(BufferedEnumerable<T> parent)
            {
                _etor = (_parent = parent)._etor;
            }
            public T Current
            {
                get { return _current; }
            }
            object IEnumerator.Current
            {
                get { return _current; }
            }
            public void Dispose()
            {
                _etor.Dispose();
            }
            public bool MoveNext()
            {
                if(_etor.MoveNext())
                {
                    _parent._buffer.Add(_current = _etor.Current);
                    return true;
                }
                _parent._etor = null;
                return false;
            }
            void IEnumerator.Reset()
            {
                throw new NotSupportedException();
            }
        }
        private class IncompleteEnumerator : IEnumerator<T>
        {
            private readonly BufferedEnumerable<T> _parent;
            private readonly IEnumerator<T> _etor;
            private IEnumerator<T> _colEtor;
            private T _current;
            public IncompleteEnumerator(BufferedEnumerable<T> parent)
            {
                _colEtor = (_parent = parent)._buffer.GetEnumerator();
                _etor = parent._etor;
            }
            public T Current
            {
                get { return _current; }
            }
            object IEnumerator.Current
            {
                get { return _current; }
            }
            public void Dispose()
            {
                _etor.Dispose();
            }
            public bool MoveNext()
            {
                if(_colEtor != null)
                {
                    if(_colEtor.MoveNext())
                    {
                        _current = _colEtor.Current;
                        return true;
                    }
                    _colEtor = null;
                }
                if(_etor.MoveNext())
                {
                    _parent._buffer.Add(_current = _etor.Current);
                    return true;
                }
                _parent._etor = null;
                return false;
            }
            void IEnumerator.Reset()
            {
                throw new NotSupportedException();
            }
        }
        private readonly ICollection<T> _buffer;
        private IEnumerable<T> _source;
        private IEnumerator<T> _etor;
        internal BufferedEnumerable(IEnumerable<T> source)
        {
            var asCol = source as ICollection<T>;
            if(asCol != null)
                _buffer = asCol;
            else
            {
                _buffer = new List<T>();
                _source = source;
            }
        }
        /// <summary>
        /// Returns an <see cref="IEnumerator{T}"/> that iterates through the enumeration.
        /// </summary>
        /// <returns>The <see cref="IEnumerator{T}"/>.</returns>
        public IEnumerator<T> GetEnumerator()
        {
            if(_source != null)
            {
                _etor = _source.GetEnumerator();
                _source = null;
                return new Enumerator(this);
            }
            if(_etor == null)
                return _buffer.GetEnumerator();
            return new IncompleteEnumerator(this);
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
    static partial class MoreEnumerable
    {
        /// <summary>
        /// Returns a <see cref="BufferedEnumerable{T}"/> that creates an in-memory
        /// copy of the enumeration on first iteration, if it is not already an
        /// in-memory source.
        /// </summary>
        /// <param name="source">The source sequence.</param>
        /// <returns>A <see cref="BufferedEnumerable{T}"/>.</returns>
        public static BufferedEnumerable<T> ToBuffer<T>(this IEnumerable<T> source)
        {
            BufferedEnumerable<T> alreadyBuffer = source as BufferedEnumerable<T>;
            return alreadyBuffer ?? new BufferedEnumerable<T>(source);
        }
    }
}
